/*
 * Author: yuwei.mu@gmail.com
 * Date: 2013-05
 */ 
try {
  document.domain = "wzhi.com";
} catch(e) {}

function Cookie(name, value) {
  if (value) {
    var s = name + '=' + encodeURIComponent(value) + ';domain=.wzhi.com;path=/';
    document.cookie = s;
  } else {
    var v = document.cookie.match( '(^|;) ?' + name + '=([^;]*)(;|$)');
    if(v) {
      return decodeURIComponent(v[2]);
    }
  }
  return null;
}

(function() {
function GetChannel(channel_id) {
  if (typeof window.CHANNEL_LIST == 'undefined') {
    logger.warn('no CHANNEL_LIST=' + (typeof CHANNEL_LIST));
    return window;
  }

  if (isNaN(g_load_balance_key)) {
    logger.warn('no g_load_balance_key=' + (typeof g_load_balance_key));
    return window;
  }
  if (g_load_balance_key > MAX_SUB_CHANNEL_KEY) {
    logger.info('GetChannel use global domain, g_load_balance_key=' + g_load_balance_key);
    return window;
  }

  if (isNaN(channel_id)) {
    logger.warn('GetChannel bad param ' + channel_id);
    return;
  }

  logger.info('GetChannel g_load_balance_key=' + g_load_balance_key + ' use subdomain ' + channel_id);

  var channel_name = 'im_channel_' + channel_id;
  var pw = parent ? parent : window;
  var ch = pw.frames[channel_name];
 
  var channel_border = document.getElementById('channel_border');
  if (channel_border && channel_border.style.display == 'none') {
    with(channel_border.style) {
      display = 'block';
      position = 'absolute';
      left = '9pt';
      top = "22pt";
      width = 3 + CHANNEL_LIST.length * 10 + 'pt';
      height = "11pt";
      border = '1px solid #c2121f';
    }
  }

  if (!ch) {
    logger.info('create new channel ' + channel_name);
    ch = pw.document.createElement('iframe');
    with(ch.style) {
      position = 'absolute';
      if (channel_border) {
        left = 12 + channel_id * 10 + 'pt';
      } else {
        left = '-3000pt';
      }
      top = "23pt";
      width = "8pt";
      height = "8pt";
      border = '1px solid green';
    }
    
    ch.name = ch.id = channel_name;
    ch.src = '/channel_' + channel_id + '/ajax-proxy.html';
    ch.frameBorder = 0;
    pw.document.body.appendChild(ch);
  }
  return ch;
}

function SelectActiveId() {
  var cc = this.candidates_.length;
  if (cc <= 0) {
    this.candidates_ = this.eliminated_;
    this.eliminated_ = [];
    cc = this.candidates_.length;
  }
  if (cc <= 0) {
    return this.active_id_; // 保持不变
  }
  this.eliminated_[this.eliminated_.length] = this.active_id_;
  var idx = (new Date()).getTime() % this.candidates_.length;
  this.active_id_ = this.candidates_[idx];

  this.candidates_.splice(idx, 1);
  if (window.logger) {
    logger.info('channel active_id_ = ' + this.active_id_);
  }
  return this.active_id_;
}

function HAXhrFactory() {
  if (typeof CHANNEL_LIST == 'undefined') {
    return;
  }
  var active_idx = (new Date()).getTime() % CHANNEL_LIST.length;
  this.active_id_ = CHANNEL_LIST[active_idx];
  this.candidates_ = CHANNEL_LIST.slice(0, active_idx).concat(CHANNEL_LIST.slice(active_idx + 1));
  this.eliminated_ = [];
  this.null_count_ = 0;
}

function UpdateChannelBorder(id, active) {
  var channel_border = document.getElementById('channel_border');
  if (channel_border) {
    var pw = parent ? parent : window;
    var ch = pw.document.getElementById('im_channel_' + id);
    if (ch && ch.style) {
      ch.style.border = (active ? '1px solid green' : '1px solid red');
    }
  }
}

HAXhrFactory.prototype.RotateChannel = function() {
  UpdateChannelBorder(this.active_id_, false);
  this.active_id_ = SelectActiveId.apply(this);
  UpdateChannelBorder(this.active_id_, true);

  logger.warn('RotateChannel to ' + this.active_id_ 
        + ' candidates=' + this.candidates_.join()
        + ' eliminated=' + this.eliminated_.join());
}

HAXhrFactory.prototype.ActiveChannelId = function() {
  return this.active_id_;  
}

HAXhrFactory.prototype.Create = function() {
  if (g_client_type == 'webmobile') {
    return $.ajax;
  }

  if (this.null_count_ > 100) {
    this.active_id_ = SelectActiveId.apply(this);
    this.null_count_ = 0;
  }
  try {
    return GetChannel(this.active_id_).$.ajax;
  } catch(e) {
    ++this.null_count_;
    return null;
  }
}

window.g_xhr_factory = new HAXhrFactory();
try{g_xhr_factory.Create();}catch(e){}

})();

(function(){
function Logger(t, l, h, w) {
  this.cnt = 0;
  this.el = document.createElement('div');
  this.el.className = 'debug';

  with(this.el.style) {
    fontSize = '9pt';
    fontFamily = 'Courier, "Courier New", monospace';
    position = 'absolute';
    textAlign = 'left';
    top = t;
    left = l;
    width = w;
    height = h;
    border = '1px solid';
    color = '#eee';
    backgroundColor = '#111';
    verticalAlign = 'bottom';
    overflow = 'auto';
    display = 'none';	
  }
  this.hidden_ = true;
  document.body.appendChild(this.el);
}

Logger.prototype.show = function() {
  this.el.style.display = '';	
  this.hidden_ = false;
}

Logger.prototype.hide = function() {
  this.el.style.display = 'none';	
  this.hidden_ = true;
}

Logger.prototype.reset = function() {
  this.cnt = 0;
  this.el.empty();
}

Logger.prototype.println = function(str, color) {
  if (this.hidden_) {
    return;
  }
  if (this.cnt ++ > 500) {
    this.cnt = 0;
    this.el.innerHTML = '';
  }

  var node = document.createElement('span');
  node.appendChild(document.createTextNode(str));
  if (color) {
    node.style.color = color;
  }
  this.el.appendChild(node);

  node = document.createElement('br');
  this.el.appendChild(node);
  this.el.scrollTop = this.el.scrollHeight;
}

Logger.prototype.debug = function(message) {
  this.println("[DEBUG] " + message, '#efe');
}

Logger.prototype.info= function(message) {
  this.println("[INFO] " + message, '#bfb');
}

Logger.prototype.dump = function(o) {
  for(var k in o) {
    this.debug('obj[\'' + k + '\']=' + o[k]);
  }
}

Logger.prototype.warn = function(message) {
  this.println("[WARN] " + message, '#bb3');
}

Logger.prototype.assert = function(message, test) {
  this.println(message + (test ? ' PASSED' : ' FAILED'), test ? '#5a5' : '#a55');
}

Logger.prototype.error = function(message) {
  this.println("[ERROR] " + message, '#d44');
  this.report(message);
}

function UidCookie() {
  return Cookie('qimrid') + '-' + Cookie('qimtid');
}

Logger.prototype.report = function(message) {
  var img = document.createElement("img");
  message += ',u=' + UidCookie() + ',t=' + g_client_type;
  img.setAttribute("src", 'http://imstatic.bang.360.cn/stat/' + message); 
  img.setAttribute("width","0");
  img.setAttribute("height","0");
  img.style.dispaly = 'none';

  setTimeout(function() {
      document.body.appendChild(img);
    }, 50 + 100 * Math.random());
}

window.Logger = Logger;

$(document).ready(function() {
  window.logger = new Logger('50px', '10px', '590px', '800px');
});

})();


(function(){

var CHANNEL_EVENT = {
  CE_LOGIN_OK : 'ce_login_ok',
  CE_LOGIN_ERR: 'ce_login_err',
  CE_LOGOUT_OK: 'ce_logout_ok',
  CE_SEND_OK  : 'ce_send_ok',
  CE_SEND_ERR : 'ce_send_err',
  CE_POLL_OK  : 'ce_poll_ok',
  CE_POLL_ERR : 'ce_poll_err',
  CE_SYSTEM_MSG: 'ce_system_msg',
  CE_PRESENCE_MSG : 'ce_presence_msg',
  CE_TYPING_MSG   : 'ce_typing_msg',
  CE_UNKNOWN  : 'ce_unknown' 
};
window.CHANNEL_EVENT = CHANNEL_EVENT;

function AjaxChannel(url_read, url_write) {
  this.event_subscribers_ = {};
  this.xhr_poll_ = null;
  this.xhr_send_ = null;
  this.url_read_ = url_read;
  this.url_write_ = url_write;
  this.timeout_ = 270000;
  this.poll_error_count_ = 0;
  this.xhr_send_queue_ = [];
}

function TriggerEvent(ev) {
  if (!ev) {
    logger.debug('TriggerEvent no arg');
    return;
  }

  for(var key in this.event_subscribers_) {
    var f = this.event_subscribers_[key];
    f && f(ev);
  }
}

AjaxChannel.prototype.AddEventSubscriber = function(key, callback) {
  this.event_subscribers_[key] = callback;
}

function GetQihuTicket() {
  var ret = '';
  if(Cookie('Q'))
    ret += Cookie('Q');
  if(Cookie('T'))
    ret += Cookie('T');
  return ret;
}

function CheckQihuTicket() {
  var t = GetQihuTicket();
  var This = this;
  if (t != this.tickets_) {
    Cookie('qimrid', '0');
    this.AbortRead();
    setTimeout(function() { This.AsyncRead(); }, 0);
    return;
  }

  setTimeout(function() {
    CheckQihuTicket.apply(This); 
  }, 1000);
}

function CheckSendQueue() {
  var q = this.xhr_send_queue_;
  if (q.length == 0) {
    logger.info('send queue empty');
    return;
  }
  var data = q[0];
  this.xhr_send_queue_ = q.slice(1);
  logger.info('dequeue message ' + Stringify(data));
  this.AsyncWrite(data);
}

AjaxChannel.prototype.AsyncWrite = function(data) {
  var This = this;
  var channel = this;
  var message = data.message;
  var view_id = data.view_id;

  if (this.xhr_send_ == null) {
    var xhr_send = g_xhr_factory.Create();
    var url_base = '';
    if (g_client_type == 'webmobile') {
      url_base = 'http://w' + g_xhr_factory.ActiveChannelId() + '.im.bang.360.cn';
    }
    if (xhr_send == null) {
      logger.error("xhr_send_null");
      setTimeout(function(){This.AsyncWrite(data)}, 50);
      return;
    }
    var retry_timeout = data.timeout;
    if (data.retry >= 3) {
      retry_timeout += data.retry * 1000;
    }
    this.xhr_send_ = xhr_send({
        url: url_base + this.url_write_ + '?r=' + Math.random(),
        type: 'POST',
        timeout : retry_timeout,
        dataType : 'text',
        data : 'receiver=' + data.receiver + '&message=' + encodeURIComponent(data.message) 
               + '&s=' + data.created_at + '&type=' + data.mtype + (data.via_shop ? '&vs=' + data.via_shop : ''),
        success : function(rsp_txt) {
          if (!isNaN(data.retry)) {
            logger.report("send_ok_on_retry=" + data.retry);
          }

          eval("var rsp_obj=" + rsp_txt + ';');
          rsp_obj.view_id = view_id;

          if (data.mtype != 'typing') {
            TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_SEND_OK, rsp : rsp_obj}]);
          } else {
            logger.warn('typing message do not trigger CE_SEND_OK');
          }

          This.xhr_send_ = null;
          CheckSendQueue.apply(This);
          logger.report("send_ok");
        },
        error : function(xhr, text_status, err) {
          var MAX_RETRY = 4; // 最多重试4次，共计5次

          var stcode = parseInt(xhr.status);
          if (stcode / 100 == 4) {
            data.retry = MAX_RETRY + 1;
          }

          if (stcode == 401) { // 401的请求，要掐poll, 并立即重启新的poll
            This.AbortRead();
            setTimeout(function() { This.AsyncRead(); }, 0);
          }

          if (isNaN(data.retry)) {
            data.retry = 1;
            logger.warn("send_err, begin retry=" + data.retry);
            This.xhr_send_queue_[This.xhr_send_queue_.length] = data;
          } else if (data.retry < MAX_RETRY) {
            data.retry += 1;
            logger.warn("send_err, set retry=" + data.retry);
            This.xhr_send_queue_[This.xhr_send_queue_.length] = data;
          } else {
            if (data.mtype != 'typing') {
              TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_SEND_ERR, view_id: view_id, message: message, errmsg:text_status}]);
            }
          }

          This.xhr_send_ = null;
          CheckSendQueue.apply(This);
          logger.report("send_err?set_retry=" + data.retry);
        }
      });
  } else {
    logger.warn('queued message ' + Stringify(data));
    this.xhr_send_queue_[this.xhr_send_queue_.length] = data;
  }
}

AjaxChannel.prototype.StopReading = function(key, callback) {
  this.stopped_ = true;
  var This = this;
  if (window.g_duplicate_conn_err) {
    logger.error("duplicate-client-conn-fixed");
  }
  setTimeout(function() { This.stopped_ = false; }, 5000);
  this.AbortRead();
  this.FixIeBugRead();
}

AjaxChannel.prototype.FixIeBugRead = function(key, callback) {
  var xhr = g_xhr_factory.Create();
  if (xhr == null) {
    return;
  }
  xhr({
      url: this.url_read_ + "?rx=" + Math.random(),
      type: 'GET',
      dataType : 'text',
      timeout : 3,
      success : function(){},
      error : function(){}
     });
}

AjaxChannel.prototype.IsReading = function(key, callback) {
  return !this.stopped_;
}

AjaxChannel.prototype.AbortRead = function() {
  if (this.xhr_poll_) {
    this.xhr_poll_.abort();
    this.xhr_poll_ = null;
  }
}

AjaxChannel.prototype.AsyncRead = function(timeout) {
  if (this.stopped_ || this.xhr_poll_) {
    return;
  }
  if (timeout > 0) {
    this.timeout_ = timeout;
  }

  var This = this;

  var xhr_poll = g_xhr_factory.Create();
  if (xhr_poll == null) {
    setTimeout(function(){This.AsyncRead(timeout)}, 50);
    return;
  }

  var url_base = '';
  if (g_client_type == 'webmobile') {
    url_base = 'http://w' + g_xhr_factory.ActiveChannelId() + '.im.bang.360.cn';
  }
  // this.xhr_poll_ = xhr;
  this.xhr_poll_ = xhr_poll({
      url: url_base + this.url_read_ + "?r=" + Math.random(),
      type: 'GET',
      dataType : 'text',
      timeout : this.timeout_,
      success : function(rsp_txt) {
        eval("var rsp_obj=" + rsp_txt + ';');
        var LOGIN_SUCCESS = 1;
        var IM_MESSAGE = 4;
        var ACCESS_SWITCH = 10;
        if (rsp_obj.type == LOGIN_SUCCESS) {
          TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_LOGIN_OK, rsp:rsp_obj}]);
          logger.report("login_ok");
        } else if (rsp_obj.type == ACCESS_SWITCH) {
          logger.info('access server switch ok.');
          logger.report("server_switch_ok");
        } else {
          var talk_messages = [];
          var system_messages = [];
          var presence_messages = [];
          var typing_messages = [];
          for(var i = 0; i < rsp_obj.length; ++i) {
            var msg = rsp_obj[i];
            switch(msg.subtype) {
            case 0:
              talk_messages[talk_messages.length] = msg;
              break;
            case 1:
            case 2:
            case 5:
              msg.type = "remind";
              system_messages[system_messages.length] = msg;
              break;
            case 3:
              logger.info('PRESENCE EVENT ' + msg.type);
              presence_messages[presence_messages.length] = msg;
              break;
            case 4:
              logger.info('TYPING EVENT ' + msg.type);
              typing_messages[typing_messages.length] = msg;
              break;
            default:
              logger.info('UNKNOWN EVENT');
            }
          }
          if (talk_messages.length) {
            TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_POLL_OK, rsp:talk_messages}]);
          }
          if (system_messages.length) {
            TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_SYSTEM_MSG, rsp:system_messages}]);
          }
          if (presence_messages.length) {
            TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_PRESENCE_MSG, rsp:presence_messages}]);
          }
          if (typing_messages.length) {
            TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_TYPING_MSG, rsp:typing_messages}]);
          }
        }

        setTimeout(function() { This.AsyncRead(); }, 0);
        This.xhr_poll_ = null;
        This.poll_error_count_ = 0;
      },
      error : function(xhr, text_status, err) {
        logger.println("xhr_poll error=" + text_status + ', ' + err);
        var interval = null;
        if (text_status == 'timeout') {
          interval = 1;
          This.poll_error_count_ = 0;
        } else {
          g_xhr_factory.RotateChannel();

          ++This.poll_error_count_;
          var POLL_ERROR_LIMIT = 20;
          const_base = 1500;
          rand_base = 1000 + This.poll_error_count_ * 500;
          logger.report("poll_err");
          if (This.poll_error_count_ < POLL_ERROR_LIMIT) {
            TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_POLL_ERR}]);
          } else if (This.poll_error_count_ == POLL_ERROR_LIMIT) {
            logger.error("login_err_on_poll_" + This.poll_error_count_);
            TriggerEvent.apply(This, [{type:CHANNEL_EVENT.CE_LOGIN_ERR}]);
          } else {
            logger.warn("login err on poll " + This.poll_error_count_);
          }
          interval = const_base + Math.random() * rand_base;
        }
        logger.println("poll_error_count_=" + This.poll_error_count_ + " next_poll_interval=" + interval);
        setTimeout(function() { This.AsyncRead(); }, interval);
        This.xhr_poll_ = null;
      }
    });

  this.tickets_ = GetQihuTicket();
  setTimeout(function() {
    CheckQihuTicket.apply(This); 
  }, 1000);
}

window.AjaxChannelUnitTest = function(write_count) {
  var channel = new AjaxChannel('/subscribe', '/publish');

  var counters = {};
  counters[CHANNEL_EVENT.CE_SEND_OK] = 0;
  counters[CHANNEL_EVENT.CE_POLL_OK] = 0;
  channel.AddEventSubscriber('test', function(ev){
      ++counters[ev.type];
    });

  channel.AsyncRead(270000);

  var repeat = 0;
  function RepeatRun() {
    if (++repeat > write_count) {
      logger.assert('CHANNEL_EVENT.CE_SEND_OK ' + counters[CHANNEL_EVENT.CE_SEND_OK] + '==' + write_count,
          counters[CHANNEL_EVENT.CE_SEND_OK] == write_count);
      logger.assert('CHANNEL_EVENT.CE_POLL_OK ' + counters[CHANNEL_EVENT.CE_POLL_OK] + '==' + write_count,
          counters[CHANNEL_EVENT.CE_POLL_OK] == write_count);
      return;
    }
    if (repeat % 10 == 1) {
      logger.assert('CE_SEND_OK=' + counters[CHANNEL_EVENT.CE_SEND_OK] + ', CE_POLL_OK=' + counters[CHANNEL_EVENT.CE_POLL_OK],
          counters[CHANNEL_EVENT.CE_POLL_OK] == counters[CHANNEL_EVENT.CE_SEND_OK]);
    }

    channel.AsyncWrite({receiver: 100001, message: 'hello' + repeat, timeout: 1000});
    setTimeout(RepeatRun, 500);
  }
  setTimeout(RepeatRun, 500);
}

window.AjaxChannel = AjaxChannel;

})();

(function(){
function Time() {
  return (new Date()).getTime();
}

function ChannelUpdateTime(timestamp) {
  if (timestamp) {
    Cookie('qimcts', timestamp);
  } else {
    var t = Cookie('qimcts');
    return t ? parseInt(t) : 0;
  }
}

var g_check_count = 0;
function CheckConnection() {
  // TODO : 可能产生连接冲突, 应检测，并放弃自己的连接. server端也需要有干净的清理
  var channel_time = ChannelUpdateTime();
  if (this.timestamp_ > 0) {
    if (channel_time != this.timestamp_) {
      logger.error("duplicate-client-conn");
      window.g_duplicate_conn_err = true;
      this.channel_.StopReading(); 
      this.timestamp_ = 0;
    } else {
      this.timestamp_ = Time();
      ChannelUpdateTime(this.timestamp_);
    }
  } else {
    var cur_time = Time();
    if (cur_time - channel_time > 2000) {
      this.timestamp_ = cur_time;
      ChannelUpdateTime(cur_time);
      logger.debug('Start reading ' + cur_time);
      this.AsyncRead(this.read_timeout_);
    } else {
      if (g_check_count++ == 0) {
        logger.debug('Somebody else is reading.');
      }
    }
  }

  var This = this;
  setTimeout(function() {
    CheckConnection.apply(This); 
  }, 200 + Math.random() * 200);
}


function MutexAjaxChannel(channel) {
  this.channel_ = channel;
  this.timestamp_ = 0;

//// dumplicate connection simulation
//var T = this;
//setTimeout(function(){
//    T.timestamp_ = 10000;
//    T.AsyncRead();
//  }, 6000);

  this.read_timeout_ = 270000;
  CheckConnection.apply(this);
}


MutexAjaxChannel.prototype.StopReading = function(key, callback) {
  if (this.timestamp_ > 0) {
    this.channel_.StopReading(); 
    ChannelUpdateTime(0);
    this.timestamp_ = 0;
  }
}

MutexAjaxChannel.prototype.IsReading = function(key, callback) {
  return this.timestamp_ > 0;
}

MutexAjaxChannel.prototype.AbortRead = function() {
  if (this.timestamp_ > 0) {
    this.channel_.AbortRead(); 
  }
}

MutexAjaxChannel.prototype.AddEventSubscriber = function(key, callback) {
  this.channel_.AddEventSubscriber(key, callback); 
}

MutexAjaxChannel.prototype.AsyncWrite = function(data) {
  if (this.timestamp_ > 0) {
    this.channel_.AsyncWrite(data); 
  }
}

MutexAjaxChannel.prototype.AsyncRead = function(timeout) {
  if (this.timestamp_ > 0) {
    this.read_timeout_ = timeout;
    this.channel_.AsyncRead(timeout); 
  }
}

window.MutexAjaxChannel = MutexAjaxChannel;

})();

(function() {

function JsonEscape(str) {
  // TODO : json 特殊字符集合？
  str = str.replace(/\\/g, '\\\\');
  str = str.replace(/"/g, '\\"');
  str = str.replace(/\t/g, '\\t');
  str = str.replace(/\n/g, '\\n');
  str = str.replace(/\r/g, '\\r');
  str = str.replace(/\f/g, '\\f');
  return str;
}
function NaiveStringify(o) {
  if(!o) return null;
  var is_array = o instanceof Array;
  var res = '{';
  if (is_array) {
    res = '['; 
  }
  var m_idx = 0;
  for(var m in o)
  {
    if (m_idx++ > 0) {
      res += ','
    }
    if (!is_array) {
      res += m + ':';
    }
    if (typeof o[m] == 'object') {
      res += NaiveStringify(o[m]);
    } else if(typeof o[m] == 'string') {
      res += '"' + JsonEscape(o[m]) + '"';
    } else {
      res += o[m];
    }
  }
  
  res += (is_array ? ']' : '}');
  return res;
}

function NaiveParse(str) {
  var o = null;

  if (str) {
    try {
      eval('var o = ' + str + ';');
    } catch (e) {
      logger.error('parse_error,str=' + str + '&msg=' + e);
    }
  }
  return o;
}
window.Parse = null;
window.Stringify = null;

if (window.JSON && JSON.parse && JSON.stringify) {
  Parse = JSON.parse;
  Stringify = JSON.stringify;
} else {
  Parse = NaiveParse;
  Stringify = NaiveStringify;
}

})();

(function() {

if (window.localStorage 
    || !/MSIE/.test(navigator.userAgent)){
  return;
}

var UserData = function(file_name) {
  if (!file_name) {
    file_name="user_data_default";
  }
  var dom = document.createElement('input');
  dom.type = "hidden";
  dom.addBehavior("#default#userData");
  document.body.appendChild(dom);
  dom.save(file_name);
  this.file_name = file_name;
  this.dom = dom;
  return this;
};

UserData.prototype = {
  setItem:function(k, v) {
    this.dom.setAttribute(k,v);
    this.dom.save(this.file_name);
  },
  getItem:function(k) {
    this.dom.load(this.file_name);
    return this.dom.getAttribute(k);
  },
  removeItem:function(k) {
    this.dom.removeAttribute(k);
    this.dom.save(this.file_name);
  },
  clear:function() {
    this.dom.load(this.file_name);
    var now = new Date();
    now = new Date(now.getTime()-1);
    this.dom.expires = now.toUTCString();
    this.dom.save(this.file_name);
  }
};

$(document).ready(function() {
  window.localStorage = new UserData("qim_storage");
});

})();


window.LocalStore = {
  Write : function(key, value) {
    if (window.localStorage) {
      try {
        localStorage.setItem(key, Stringify(value));
        logger.debug("localStorage setItem ok. key=" + key);
      } catch(e) {
        logger.error("localStorage setItem err= " + e + " key=" + key);
        return false;
      }
      return true;
    }
    return false;
  },
  Read : function(key) {
    if (window.localStorage) {
      var s = null;
      try {
        s = localStorage.getItem(key);
        logger.debug("localStorage getItem ok. key=" + key);
      } catch(e) {
        logger.error("localStorage getItem err=" + e + " key=" + key);
        return null;
      }
      return Parse(s);
    }
    return null;
  },
  Remove : function(key) {
    if (window.localStorage) {
      try {
        localStorage.removeItem(key);
      } catch(e) {
        logger.warn("localStorage removeItem err:" + e);
      }
    }
  }
};


(function() {
function GetProducerSeq() {
  var seq = Cookie(this.name_ + 'seq');
  if (seq) {
    return parseInt(seq);
  }
  return 0;
}

function IncProcuderSeq() {
  var k = this.name_ + 'seq';
  var seq = Cookie(k);
  var new_seq = seq ? (1 + parseInt(seq)) : 1;
  Cookie(k, new_seq);
  logger.debug('IncProcuderSeq ' + seq + " to " + new_seq);
}

var STORAGE_EVENT = {
  SE_SEND     : 'se_send',
  SE_SEND_OK  : 'se_send_ok',
  SE_SEND_ERR : 'se_send_fail',
  SE_POLL_OK  : 'se_poll_ok',
  SE_POLL_ERR : 'se_poll_err',
  SE_LOGIN_OK : 'se_login_ok',
  SE_LOGIN_ERR: 'se_login_err',
  SE_SYSTEM_MSG: 'se_system_msg',
  SE_PRESENCE_MSG : 'se_presence_msg',
  SE_TYPING_MSG   : 'se_typing_msg',
  SE_UNKONW   : 'se_unknown'
};
window.STORAGE_EVENT = STORAGE_EVENT;

var MAX_STORE_SLOTS = 20;
function StorageProducer(name) {
  this.name_ = name;
}

function ClearOldData(seq) {
  if (seq > 200) {
    return;
  }
  var k = 'qim' + seq;
  if (LocalStore.Read(k) == null) {
    return;
  }
  logger.info('old data removed ' + k);
  LocalStore.Remove(k);
  setTimeout(function() {
      ClearOldData(++seq);
    }, 1500);
}

$(document).ready(function() {
  setTimeout(function() {
      ClearOldData(100);
    }, 10000);
});


function GetStorageKey(name, sid) {
  var kid = (sid % MAX_STORE_SLOTS);
  if (kid < 10) {
    kid = '0' + kid;
  }
  return name + 'data_' + kid;
}

StorageProducer.prototype.Produce = function(data) {
  var sid = GetProducerSeq.apply(this) + 1;
  var k = GetStorageKey(this.name_, sid);

  data.created_at = (new Date()).getTime();
  var MAX_WRITE_RETRY = 3;
  for (var i = 0; i < MAX_WRITE_RETRY; ++i) {
    logger.debug("Produce event " + k + "=" + Stringify(data));
    LocalStore.Remove(k);
    if (LocalStore.Write(k, data))
      break;
  }
  
  // seq不可以延时更新!会导致覆盖
  IncProcuderSeq.apply(this);
}

function StorageConsumer(name) {
  this.name_ = name;
  this.handlers_ = {};
  this.last_consumed_ = GetProducerSeq.apply(this);
}

var g_stale_count = 0;
var g_current_stale_retry = 0;
StorageConsumer.prototype.Consume = function() {
  var producer_seq = GetProducerSeq.apply(this);
  if (producer_seq > this.last_consumed_) {
    ++this.last_consumed_;
    var k = GetStorageKey(this.name_, this.last_consumed_);
    var data = null;

    var MAX_READ_RETRY = 3;
    for (var i = 0; i < MAX_READ_RETRY; ++i) {
      data = LocalStore.Read(k);
      if (data != null)
        break;
    }

    if (data == null) {
      // do nothing
    } else {
      var now = (new Date()).getTime();
      var expire_interval = (producer_seq + 2 - this.last_consumed_) * 1000 + 10000;
      logger.debug("now=" + now + " w_at=" + data.created_at + " interval=" 
                   + (now - data.created_at) + " expire_interval=" + expire_interval);

      if (!data.created_at || (now - data.created_at > expire_interval)) {
        logger.warn("Stale event " + k + "=" + Stringify(data)
                   + " interval=" + (now - data.created_at) + " expire_interval=" + expire_interval);

        if (++g_stale_count > 200) {
          if (g_stale_count % 100 == 32) {
            logger.error('g_stale_count=' + g_stale_count);
          }
        }
        if(++g_current_stale_retry < 7) {
          --this.last_consumed_;
        } else {
          logger.error('k=' + k + '&stale_retry_err=' + g_current_stale_retry);
          g_current_stale_retry = 0;
        }
      } else {
        logger.debug("Consume event " + k + "=" + Stringify(data));
        if (g_current_stale_retry > 0) {
          logger.warn("Consume event " + k + " retry_on_stale=" + g_current_stale_retry);
        }
        g_current_stale_retry = 0;
        for(var key in this.handlers_) {
          var h = this.handlers_[key];
          h && h(data);
        }
      }
    }
  }
  var This = this;
  setTimeout(function() {
      This.Consume();
    }, producer_seq > this.last_consumed_ ? 50 : 100);
}

StorageConsumer.prototype.AddHandler = function(key, handler) {
  this.handlers_[key] = handler;
}

// facade for producer & consumer
function StorageQueue(name) {
  this.producer_ = new StorageProducer(name);
  this.consumer_ = new StorageConsumer(name);
}

StorageQueue.prototype.Enqueue = function(data) {
  this.producer_.Produce(data);
}

StorageQueue.prototype.StartDequeue = function() {
  this.consumer_.Consume();
}

StorageQueue.prototype.AddDequeueCallback = function(key, callback) {
  this.consumer_.AddHandler(key, (function() {
        var cb = callback;
        return function() {
          cb.apply(cb, [arguments[0]]);
        }
      })());
}

window.StorageQueueUnitTest = function(msg_send_count) {
  var queue = new StorageQueue('test_storage_');
  var msg_recv_count = 0;
  var produceq_seq_before = GetProducerSeq.apply(queue.producer_);
  
  var dequeue_count = 0;
  queue.AddDequeueCallback('debug', function(data) {
      logger.debug('consumed message : ' + data.type);
      ++ dequeue_count;
    });

  queue.StartDequeue();
  
  for(var i = 0; i < msg_send_count; ++i) {
      queue.Enqueue({type:STORAGE_EVENT.SE_SEND, sender:'1000001', content:'this is StorageQueueTest message ' + i});
  }
  setTimeout(function() {
      var producer_seq_after = GetProducerSeq.apply(queue.producer_);
      logger.assert('StorageQueue enqueue count = (' + producer_seq_after + '-' + produceq_seq_before + ') = ' + msg_send_count,
          producer_seq_after - produceq_seq_before == msg_send_count);
      logger.assert('StorageQueue dequeue count = ' + dequeue_count, dequeue_count == msg_send_count);
    }, 3000);
}

window.StorageQueue = StorageQueue;
})();


(function(){

function ChannelEventType(stroage_event_type) {
  switch(stroage_event_type) {
  case STORAGE_EVENT.SE_LOGIN_OK:
    return CHANNEL_EVENT.CE_LOGIN_OK;
  case STORAGE_EVENT.SE_LOGIN_ERR:
    return CHANNEL_EVENT.CE_LOGIN_ERR;
  case STORAGE_EVENT.SE_SEND_OK:
    return CHANNEL_EVENT.CE_SEND_OK;
  case STORAGE_EVENT.SE_SEND_ERR:
    return CHANNEL_EVENT.CE_SEND_ERR;
  case STORAGE_EVENT.SE_POLL_OK:
    return CHANNEL_EVENT.CE_POLL_OK;
  case STORAGE_EVENT.SE_POLL_ERR:
    return CHANNEL_EVENT.CE_POLL_ERR;
  case STORAGE_EVENT.SE_SYSTEM_MSG:
    return CHANNEL_EVENT.CE_SYSTEM_MSG;
  case STORAGE_EVENT.SE_PRESENCE_MSG:
    return CHANNEL_EVENT.CE_PRESENCE_MSG;
  case STORAGE_EVENT.SE_TYPING_MSG:
    return CHANNEL_EVENT.CE_TYPING_MSG;
  }
  return CHANNEL_EVENT.CE_UNKNOWN;
}

function StorageQueueCallback(data) {
  if (data == null || data.type == null) {
    logger.warn('read StorageQueue error');
    return;
  }
  logger.debug('on STORAGE_EVENT ' + Stringify(data));
  switch(data.type) {
  case STORAGE_EVENT.SE_SEND:
    this.ajax_channel_.AsyncWrite(data);
    break;
  case STORAGE_EVENT.SE_SEND_OK:
  case STORAGE_EVENT.SE_SEND_ERR:
  case STORAGE_EVENT.SE_POLL_OK:
  case STORAGE_EVENT.SE_POLL_ERR:
  case STORAGE_EVENT.SE_LOGIN_OK:
  case STORAGE_EVENT.SE_LOGIN_ERR:
  case STORAGE_EVENT.SE_SYSTEM_MSG:
  case STORAGE_EVENT.SE_PRESENCE_MSG:
  case STORAGE_EVENT.SE_TYPING_MSG:
    data.type = ChannelEventType(data.type);
    for(var key in this.event_subscribers_) {
      var f = this.event_subscribers_[key];
      f && f(data);
    }
    break;
  default:
    logger.debug('UNHANDLED StorageQueue Event, type=' + data.type);
  }
}

function AjaxChannelDebugCallback(data) {
  if (!data) {
    logger.warn('BAD Qihu AjaxCheannel Event');
    return;
  }
  switch(data.type) {
  case CHANNEL_EVENT.CE_SEND_OK:
    logger.debug('on CHANNEL_EVENT.CE_SEND_OK');
    break;
  case CHANNEL_EVENT.CE_SEND_ERR:
    logger.debug('on CHANNEL_EVENT.CE_SEND_ERR');
    break;
  case CHANNEL_EVENT.CE_POLL_OK:
    logger.debug('on CHANNEL_EVENT.CE_POLL_OK');
    break;
  case CHANNEL_EVENT.CE_POLL_ERR:
    logger.debug('on CHANNEL_EVENT.CE_POLL_ERR');
    break;
  case CHANNEL_EVENT.CE_LOGIN_OK:
    logger.debug('on CHANNEL_EVENT.CE_LOGIN_OK');
    break;
  case CHANNEL_EVENT.CE_LOGIN_ERR:
    logger.debug('on CHANNEL_EVENT.CE_LOGIN_ERR');
    break;
  case CHANNEL_EVENT.CE_SYSTEM_MSG:
    logger.debug('on CHANNEL_EVENT.CE_SYSTEM_MSG');
    break;
  case CHANNEL_EVENT.CE_PRESENCE_MSG:
    logger.debug('on CHANNEL_EVENT.CE_PRESENCE_MSG');
    break;
  case CHANNEL_EVENT.CE_TYPING_MSG:
    logger.debug('on CHANNEL_EVENT.CE_TYPING_MSG');
    break;

  default:
    logger.debug('UNHANDLED Qihu AjaxCheannel Event, type=' + data.type);
  }
}

function QihuChannel(ajax_channel) {
  this.storage_queue_ = new StorageQueue('qim');
  this.ajax_channel_ = ajax_channel;
  this.event_subscribers_ = {};

  var T1 = this;

  this.storage_queue_.AddDequeueCallback('qim', (function() {
    var T2 = T1;
    return function() {
      StorageQueueCallback.apply(T2, [arguments[0]]);
    };
  })());

  this.ajax_channel_.AddEventSubscriber('qimdebug', (function() {
    var T2 = T1;
    return function(data) {
      AjaxChannelDebugCallback.apply(T2, [data]);
    };
  })());
}

QihuChannel.prototype.StopReading = function(key, callback) {
  this.ajax_channel_.StopReading();
}
QihuChannel.prototype.IsReading = function(key, callback) {
  return this.ajax_channel_.IsReading();
}

QihuChannel.prototype.AbortRead = function() {
  this.ajax_channel_.AbortRead();
}

QihuChannel.prototype.AsyncWrite = function(data) {
  data.type = STORAGE_EVENT.SE_SEND;
  this.storage_queue_.Enqueue(data);
}

function StorageEventType(channel_event_type) {
  switch(channel_event_type) {
  case CHANNEL_EVENT.CE_LOGIN_OK:
    return STORAGE_EVENT.SE_LOGIN_OK;
  case CHANNEL_EVENT.CE_LOGIN_ERR:
    return STORAGE_EVENT.SE_LOGIN_ERR;
  case CHANNEL_EVENT.CE_SEND_OK:
    return STORAGE_EVENT.SE_SEND_OK;
  case CHANNEL_EVENT.CE_SEND_ERR:
    return STORAGE_EVENT.SE_SEND_ERR;
  case CHANNEL_EVENT.CE_POLL_OK:
    return STORAGE_EVENT.SE_POLL_OK;
  case CHANNEL_EVENT.CE_POLL_ERR:
    return STORAGE_EVENT.SE_POLL_ERR;
  case CHANNEL_EVENT.CE_SYSTEM_MSG:
    return STORAGE_EVENT.SE_SYSTEM_MSG;
  case CHANNEL_EVENT.CE_PRESENCE_MSG:
    return STORAGE_EVENT.SE_PRESENCE_MSG;
  case CHANNEL_EVENT.CE_TYPING_MSG:
    return STORAGE_EVENT.SE_TYPING_MSG;

  }
  return STORAGE_EVENT.SE_UNKONW;
}

function AjaxEvent2StorageEvent(ev) {
  var storage_queue = this;
  logger.debug('AjaxEvent2StorageEvent input type = ' + ev.type);
  switch(ev.type) {
  case CHANNEL_EVENT.CE_LOGIN_OK:
    ev.type = STORAGE_EVENT.SE_LOGIN_OK;
    break;
  case CHANNEL_EVENT.CE_LOGIN_ERR:
    ev.type = STORAGE_EVENT.SE_LOGIN_ERR;
    break;
  case CHANNEL_EVENT.CE_SEND_OK:
    ev.type = STORAGE_EVENT.SE_SEND_OK;
    break;
  case CHANNEL_EVENT.CE_SEND_ERR:
    ev.type = STORAGE_EVENT.SE_SEND_ERR;
    break;
  case CHANNEL_EVENT.CE_POLL_OK:
    ev.type = STORAGE_EVENT.SE_POLL_OK;
    break;
  case CHANNEL_EVENT.CE_POLL_ERR:
    ev.type = STORAGE_EVENT.SE_POLL_ERR;
    break;
  case CHANNEL_EVENT.CE_SYSTEM_MSG:
    ev.type = STORAGE_EVENT.SE_SYSTEM_MSG;
    break;
  case CHANNEL_EVENT.CE_PRESENCE_MSG:
    ev.type = STORAGE_EVENT.SE_PRESENCE_MSG;
    break;
  case CHANNEL_EVENT.CE_TYPING_MSG:
    ev.type = STORAGE_EVENT.SE_TYPING_MSG;
    break;

  default:
    ev.type = STORAGE_EVENT.SE_UNKONW;
  }
  storage_queue.Enqueue(ev);
}

QihuChannel.prototype.AsyncRead = function(timeout) {
  this.storage_queue_.StartDequeue('qim');

  // ajax事件一律写storage_queue_
  var This = this;
  this.ajax_channel_.AddEventSubscriber('qim', (function() { 
          var q = This.storage_queue_;
          return function() { 
            AjaxEvent2StorageEvent.apply(q, [].slice.call(arguments));
          };
    })());

  this.ajax_channel_.AsyncRead(timeout);
}

QihuChannel.prototype.AddEventSubscriber = function(key, callback) {
  this.event_subscribers_[key] = callback;
}

window.QihuChannelUnitTest = function(write_count) {
  var channel = new QihuChannel(new AjaxChannel('/subscribe', '/publish'));

  // FIXME: mutex channel 切换期间发送的消息，可能不被投递, 导致test失败
  // var channel = new QihuChannel(new MutexAjaxChannel(new AjaxChannel('/subscribe', '/publish')));

  var counters = {};
  counters[CHANNEL_EVENT.CE_SEND_OK] = 0;
  counters[CHANNEL_EVENT.CE_POLL_OK] = 0;
  channel.AddEventSubscriber('test', function(ev){
      ++counters[ev.type];
    });

  channel.AsyncRead(270000);
  function EventSeq() {
    var seq = Cookie('qimseq');
    if (seq) {
      return parseInt(seq);
    }
    return 0;
  }

  var repeat = 0;
  var begin_seq = EventSeq();
  function RepeatRun() {
    if (++repeat > write_count) {
      var end_seq = EventSeq();
      logger.assert('event_seq ' + end_seq + '-' + begin_seq + '=' + (3 * write_count), end_seq - begin_seq == 3 * write_count);

      logger.assert('CHANNEL_EVENT.CE_SEND_OK ' + counters[CHANNEL_EVENT.CE_SEND_OK] + '==' + write_count,
          counters[CHANNEL_EVENT.CE_SEND_OK] == write_count);
      logger.assert('CHANNEL_EVENT.CE_POLL_OK ' + counters[CHANNEL_EVENT.CE_POLL_OK] + '==' + write_count,
          counters[CHANNEL_EVENT.CE_POLL_OK] == write_count);
      logger.debug('Test finished.');
      return;
    }
    if (repeat % 10 == 1) {
      var end_seq = EventSeq();
      logger.assert('event_seq ' + end_seq + '-' + begin_seq + '=' + (3 * (repeat - 1)), end_seq - begin_seq == 3 * (repeat - 1));
    }

    channel.AsyncWrite({receiver:100001, content: 'hello' + repeat, timeout : 1000});
    setTimeout(RepeatRun, 500);
  }
  setTimeout(RepeatRun, 1000);
  logger.debug('Test started.');
}

window.QihuChannel = QihuChannel;

})();

(function(){
function DebugView() {
  this.console_ = new Logger('50px', '830px', '590px', '820px');
  this.console_.show();
}

DebugView.prototype.OnSendMessageSuccess = function(data) {
  this.console_.debug('OnSendMessageSuccess. data=' + Stringify(data));
}
DebugView.prototype.OnSendMessageError = function(ev) {
  this.console_.warn('OnSendMessageError. data=' + Stringify(ev));
}
DebugView.prototype.OnReceiveMessageSuccess = function(ev) {
  this.console_.debug('OnReceiveMessageSuccess. data=' + Stringify(ev));
}
DebugView.prototype.OnReceiveMessageError = function(ev) {
  this.console_.warn('OnReceiveMessageError. data=' + Stringify(ev));
}
DebugView.prototype.OnLoginSuccess = function(ev) {
  this.console_.debug('OnLoginSuccess. data=' + Stringify(ev));
}
DebugView.prototype.OnLoginError = function(ev) {
  this.console_.warn('OnLoginError. data=' + Stringify(ev));
}
DebugView.prototype.OnSystemMessage = function(ev) {
  this.console_.debug('OnSystemMessage. data=' + Stringify(ev));
}
DebugView.prototype.OnPresenceMessage = function(ev) {
  this.console_.debug('OnPresenceMessage. data=' + Stringify(ev));
}
DebugView.prototype.OnTypingMessage = function(ev) {
  this.console_.debug('OnTypingMessage. data=' + Stringify(ev));
}

window.DebugView = DebugView;
})();


(function(){
function ChatController(channel) {
  this.channel_ = channel;
  this.views_ = {};
}

var view_id = (new Date()).getTime();
ChatController.prototype.SendMessage = function(receiver, message, timeout) {
  var vid = view_id ++;
  var data = {receiver : receiver, message : message, mtype : 'msg', view_id : vid,  timeout : (timeout ? timetout : 1058)};
  this.channel_.AsyncWrite(data);
  return vid;
}

ChatController.prototype.SendTypingMessage = function(receiver, timeout) {
  var vid = view_id ++;
  var data = {receiver : receiver, message : '', mtype : 'typing', view_id : vid,  timeout : (timeout ? timetout : 1058)};
  this.channel_.AsyncWrite(data);
  return vid;
}

ChatController.prototype.StopReceiveMessage = function() {
  this.channel_.StopReading();
}

ChatController.prototype.StartReceiveMessage = function() {
  this.channel_.AsyncRead(200000 + 70000 * Math.random());
}

function ChannelViewCallback(view) {
  return function(ev) {
    var callback;
    switch(ev.type) {
    case CHANNEL_EVENT.CE_SEND_OK:
      callback = view.OnSendMessageSuccess;
      break;
    case CHANNEL_EVENT.CE_SEND_ERR:
      callback = view.OnSendMessageError;
      break;
    case CHANNEL_EVENT.CE_POLL_OK:
      callback = view.OnReceiveMessageSuccess;
      break;
    case CHANNEL_EVENT.CE_POLL_ERR:
      callback = view.OnReceiveMessageError;
      break;
    case CHANNEL_EVENT.CE_LOGIN_OK:
      callback = view.OnLoginSuccess;
      break;
    case CHANNEL_EVENT.CE_LOGIN_ERR:
      callback = view.OnLoginError;
      break;
    case CHANNEL_EVENT.CE_SYSTEM_MSG:
      callback = view.OnSystemMessage;
      break;
    case CHANNEL_EVENT.CE_PRESENCE_MSG:
      callback = view.OnPresenceMessage;
      break;
    case CHANNEL_EVENT.CE_TYPING_MSG:
      callback = view.OnTypingMessage;
      break;
    }
    if (callback) {
      callback.apply(view, [ev]);
    } else {
      logger.warn('View unhandled channel event ' + ev.type);
    }
  }
}

ChatController.prototype.AttachView = function(key, view) {
  this.views_[key] = view;
  this.channel_.AddEventSubscriber(key, ChannelViewCallback(view));
}

ChatController.prototype.DetachView = function(key) {
  // TODO : channel 支持 RemoveEventSubscriber
  delete this.views_[key]; 
  // this.channel_.RemoveEventSubscriber(key);
}

ChatController.prototype.IsAdmin = function() {
  return this.channel_.IsReading();
}

window.ChatController = ChatController;
})();


